Skip to content

feat(share): workspace-scoped collaborative sessions with consensus voting#612

Draft
doomspork wants to merge 6 commits intomainfrom
doomspork/remote-share-investigation
Draft

feat(share): workspace-scoped collaborative sessions with consensus voting#612
doomspork wants to merge 6 commits intomainfrom
doomspork/remote-share-investigation

Conversation

@doomspork
Copy link
Copy Markdown
Member

@doomspork doomspork commented May 5, 2026

Summary

Extends Claudette's 1:1 remote-control sharing into multi-user collaborative workspace sharing. Multiple participants can now join the same chat session, see each other's messages live, and (optionally) gate plan approval on unanimous consensus. The host issues workspace-scoped shares (rather than instance-wide pairing tokens), so each share grants access only to a specific subset of workspaces; revoking a share invalidates every session token it issued.

flowchart LR
  subgraph host["Host process (Tauri)"]
    HU[Host UI<br/>Zustand store]
    HB[Bridge<br/>chat/send.rs]
    SS[Embedded server<br/>claudette-server]
    RR[(RoomRegistry<br/>shared Arc)]
  end
  subgraph remote["Remote (paired)"]
    RU[Remote UI<br/>Zustand store]
  end
  HB -->|publish| RR
  SS -->|publish<br/>subscribe| RR
  RR -->|on_create hook| HU
  RR -->|tokio::broadcast| RU
  RU -.->|join_session<br/>send_chat_message<br/>vote_plan_approval| SS
Loading

Every collaborative session has a Room that owns a bounded tokio::broadcast channel. Both the Tauri host and the embedded server publish into the same room and subscribe from it; events fan out to every participant — host UI plus all remote clients — through a single source of truth. Solo / 1:1 legacy sessions skip the room entirely and keep the existing direct-emit path (zero overhead for non-collaborative use).

Headline new capabilities

  • Workspace-scoped shares with revocation: each share has a pairing token, an allowed-workspace set, and collab/consensus flags. Stopping a share immediately rejects every in-flight RPC tied to it (per-RPC revocation check in handler.rs::handle_request).
  • Plan-approval consensus (host veto + unanimous-otherwise): when ExitPlanMode fires in a consensus-required room, all unmuted participants see the plan card simultaneously and vote. Host approve/deny short-circuits; a non-host deny finalizes deny with the user's critique flowed back to the agent.
  • Turn lock: only one participant can drive the agent at a time; competing prompts are hard-rejected (composer disables on turn-started).
  • Live participant roster, author-stamped messages, share-modal management UI (mint/copy/revoke), persisted shares hydrated on startup.

Architecture: see src/room.rs for the Room / RoomRegistry core, src-server/src/collab.rs for the server-side join/leave/vote handlers, and src-tauri/src/commands/share.rs for share lifecycle. The agent-stream event payload is now a single claudette::chat::AgentStreamPayload struct serialized by both bridges (Tauri-side and server-side), so the wire shape can no longer drift between them.

15 commits, ~+4250 / −520 across ~58 files (3 new Rust modules, 4 new TS components, 1 SQL migration adding nullable author_participant_id / author_display_name to chat_messages).

Complexity Notes

  • Publish-before-subscribe race in the broadcast channel (fixed in 6a0c104 via RoomRegistry::set_on_create hook). tokio::sync::broadcast does not buffer for late subscribers — if a publisher fires before a subscriber attaches, that subscriber will never see the event. The on_create hook fires synchronously on room creation before the new Arc<Room> becomes visible in the registry, so the host's mirror task captures a Receiver before any handler can publish. Locked in by a regression test in src/room.rs.
  • Two bridges, one envelope shape — and now one struct. Both src-tauri/src/commands/chat/send.rs and src-server/src/handler.rs::handle_send_chat_message publish agent-stream events into the room. After dd5c105 they share a single claudette::chat::AgentStreamPayload struct with serde::Serialize so the wire shape stays in lockstep. Earlier in this branch the server side hand-rolled json! and one regression dropped events on the remote until 1e1db36 fixed the key — that whole class of bug is now structurally prevented.
  • Cross-process Arc<RoomRegistry> sharing: the Tauri host and embedded server live in the same OS process but use distinct State types. They share the registry via run_with_rooms(opts, rooms) in src-server/src/lib.rs. Worth verifying during review that no future refactor accidentally constructs a second registry on the server side — the on_create hook would fail to fire and we'd silently regress to the publish-before-subscribe race.
  • Backward-compatible auth + DB: the share auth flow keeps the legacy 1:1 pairing flow working (no migration needed). The DB migration is additive (ALTER TABLE chat_messages ADD COLUMN ... NULL) — existing rows read back with NULL author fields, treated as "host" legacy.
  • AskUserQuestion participates in collaboration: remotes see the question card and submit answers through the submit_agent_answer collaboration RPC, using the same consensus snapshot and vote propagation path as other shared prompts. This lets every required voter answer before the agent continues.

Test Steps

Setup: Restart cargo tauri dev on the host. To exercise multi-participant flows, also run cargo tauri dev on a second machine (or VM) with this branch checked out.

  1. Solo-mode regression check — Without starting any share, send a chat message and use the workspace normally. Behavior should be byte-for-byte identical to main (no room is created when no share is active; state.rooms.get(...) returns None and code paths fall back to the original direct-emit logic).
  2. Mint a share — Open the share modal (Sidebar → Share button). Click New share, pick at least one workspace, check Collaborative mode and Require unanimous plan approval, click Mint share. A connection string claudette://host:port/<token> should appear; copy it.
  3. Pair from remote — On the second machine: Sidebar → Add remote. Paste the connection string. The shared workspace appears under SEANS-MACBOOK-PRO.LOCAL (or your hostname). Open it, then open a chat session.
  4. Participants populate — On the host, the share modal should show 1 connected. The chat panel header should show both participants in the roster.
  5. Two-way chat — From the remote, send ping. The user message should render on both ends; the agent's response should stream into both UIs in lockstep. Repeat from the host.
  6. Turn lock — While the agent is streaming a response (composer should be disabled on the prompter side), try to send a message from the other participant. Their composer should also be disabled with a User X is asking… indicator. After the turn ends, both composers re-enable.
  7. Plan consensus — Send a prompt that triggers plan mode (e.g., Plan how you'd refactor X — use ExitPlanMode). When the plan card appears on both ends, click View plan from the remote — the plan content should expand inline. Then have the remote click Deny with a critique. The agent should receive the critique and produce a revised plan on the next turn. Test the inverse: both Approve → agent proceeds.
  8. Host veto — Open another plan vote. Have the host click Deny first — the vote should short-circuit immediately even if the remote hasn't voted.
  9. Persisted shares — Stop the host's cargo tauri dev, restart it, and reopen the share modal. The previously-minted share should appear immediately (terminal logs [share] Hydrating N persisted share(s)).
  10. Revocation — Click Stop on the share. The remote's next RPC should fail with This share has been revoked by the host.

Checklist

  • Tests added/updated (regression test for publish-before-subscribe race in src/room.rs; collab slice + reducer tests in src/ui/src/stores/useAppStore.test.ts; existing path-validation in read_plan_file server handler) — full integration test for multi-participant flow still pending
  • Documentation — architecture overview in this PR body is the canonical reference until we move to a docs page
  • Diagnostic [collab-trace] eprintlns removed (commit eafb92a)
  • AgentStreamPayload extracted to shared claudette::chat module (commit dd5c105)

Copilot AI review requested due to automatic review settings May 5, 2026 01:26
@codecov
Copy link
Copy Markdown

codecov Bot commented May 5, 2026

Codecov Report

❌ Patch coverage is 45.71063% with 848 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.50%. Comparing base (a260930) to head (f412722).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #612      +/-   ##
==========================================
- Coverage   80.82%   79.50%   -1.33%     
==========================================
  Files          79       82       +3     
  Lines       26424    27808    +1384     
==========================================
+ Hits        21358    22109     +751     
- Misses       5066     5699     +633     
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds workspace-scoped sharing that upgrades Claudette’s legacy 1:1 remote-control pairing into multi-participant collaborative sessions, including room-based event fanout, turn locking, participant roster, and optional unanimous plan-approval voting.

Changes:

  • Introduces a shared Room/RoomRegistry core (Rust) and server-side collab RPCs (join_session, leave_session, vote_plan_approval) to support multi-user sessions with broadcasted events.
  • Adds UI state + components for collaboration (participants roster, consensus vote tracking, workspace status icon updates, share-management modal, collaboration settings).
  • Extends chat message schema to persist author identity (author_participant_id, author_display_name) and wires author stamping/broadcasting across host + server bridges.

Reviewed changes

Copilot reviewed 56 out of 56 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
src/ui/src/utils/reconstructTurns.test.ts Updates test fixtures to include new chat message author fields.
src/ui/src/utils/extractLatestCallUsage.test.ts Updates test fixtures for author fields.
src/ui/src/utils/compactionSentinel.test.ts Updates test fixtures for author fields.
src/ui/src/utils/checkpointUtils.test.ts Updates test fixtures for author fields.
src/ui/src/utils/chatTurnFooter.test.ts Updates test fixtures for author fields.
src/ui/src/types/remote.ts Extends remote connection types to include participant_id.
src/ui/src/types/chat.ts Extends ChatMessage with persisted author fields.
src/ui/src/stores/useAppStore.ts Registers new collaboration slice in Zustand store.
src/ui/src/stores/useAppStore.test.ts Updates store tests for author fields; includes collab-related test updates.
src/ui/src/stores/slices/collabSlice.ts Adds collaboration slice: participants roster, turn holder, consensus voting state.
src/ui/src/services/tauri.ts Adds share-management APIs (startShare, stopShare, listShares, moderation RPCs).
src/ui/src/hooks/useAgentStream.ts Adds listeners for collab events and chat-message broadcasts; updates sentinel messages with author fields.
src/ui/src/components/sidebar/WorkspaceStatusIcon.tsx Extracts/centralizes workspace sidebar status badge rendering.
src/ui/src/components/sidebar/Sidebar.tsx Uses WorkspaceStatusIcon; changes share button behavior to open modal only; updates synthetic messages with author fields.
src/ui/src/components/shared/WorkspacePanelHeader.tsx Adds ParticipantsRoster to header; derives selfParticipantId.
src/ui/src/components/settings/SettingsSidebar.tsx Adds “Collaboration” settings section entry.
src/ui/src/components/settings/SettingsPage.tsx Lazy-loads collaboration settings section.
src/ui/src/components/settings/sections/CollaborationSettings.tsx New settings page for display name + default consensus preference.
src/ui/src/components/modals/ShareModal.tsx Replaces legacy modal with workspace-scoped share list + “mint share” form.
src/ui/src/components/modals/ConfirmSetupScriptModal.tsx Updates emitted system messages to include author fields.
src/ui/src/components/command-palette/CommandPalette.tsx Updates emitted system messages to include author fields.
src/ui/src/components/chat/SessionTabs.tsx Lists chat sessions via remote RPC for remote workspaces.
src/ui/src/components/chat/planFilePath.test.ts Updates test fixtures for author fields.
src/ui/src/components/chat/PlanApprovalCard.tsx Adds consensus progress display; supports remote plan reads via read_plan_file.
src/ui/src/components/chat/ParticipantsRoster.tsx New participant roster with host-only moderation controls (kick/mute).
src/ui/src/components/chat/MessagesWithTurns.tsx Renders author-stamped user messages (“You” vs display name).
src/ui/src/components/chat/ChatPanel.tsx Calls join_session for remote collaborative sessions; updates optimistic messages with author fields.
src/ui/src/components/chat/ChatInputArea.tsx Disables composer based on collab turn-lock state; adds locked placeholders/titles.
src/ui/src/App.tsx Hydrates collaboration preferences from app settings on boot.
src/room.rs New room + registry implementation with broadcast channel, turn lock, and on-create hook + tests.
src/model/chat_message.rs Adds author fields to Rust ChatMessage model.
src/migrations/mod.rs Registers new migration for chat message author columns.
src/migrations/20260428034257_chat_message_author.sql Adds nullable author columns to chat_messages.
src/lib.rs Exposes new room module.
src/fork.rs Ensures forked chat history copies author fields.
src/db/test_support.rs Updates DB test helper to populate new author fields.
src/db/chat.rs Persists/reads new author fields; updates column list constant.
src/chat.rs Ensures assistant/sentinel messages have null author fields.
src-tauri/src/transport/ws.rs Parses participant_id from server auth responses.
src-tauri/src/state.rs Adds shared RoomRegistry, share server config state, and host display-name resolver.
src-tauri/src/remote.rs Adds derived participant_id field and helper for remote connections.
src-tauri/src/main.rs Installs room on-create hook; hydrates persisted shares; wires new share commands.
src-tauri/src/commands/share.rs Implements workspace-scoped share lifecycle + persisted hydration.
src-tauri/src/commands/remote.rs Adds participant-id plumbing, improves remote error propagation, adds kick/mute commands; updates subprocess readiness detection.
src-tauri/src/commands/mod.rs Registers new share commands module.
src-tauri/src/commands/chat/send.rs Stamps/broadcasts user messages in collab; enforces turn lock; broadcasts agent stream + permission prompts + vote opened/resolved.
src-tauri/src/commands/chat/mod.rs Re-exports record_plan_vote for host-side resolver task.
src-tauri/src/commands/chat/lifecycle.rs Updates system messages to include author fields.
src-tauri/src/commands/chat/interaction.rs Implements consensus vote recording + resolution; broadcasts vote events.
src-server/tests/env_provider_integration.rs Updates server state construction to include config.
src-server/src/ws.rs Adds shared rooms + live config to state; constructs per-connection ctx; drops participants on disconnect.
src-server/src/main.rs Removes legacy CLI subcommands; runs server with new options shape.
src-server/src/lib.rs Adds run_with_rooms and existing_config support; removes global connection-string printing.
src-server/src/handler.rs Adds per-connection authorization context; enforces share scoping + revocation; integrates collab RPC dispatch; broadcasts through rooms when present.
src-server/src/collab.rs New server-side collab RPC handlers (join/leave/vote + disconnect cleanup).
src-server/src/auth.rs Implements share-scoped auth model and participant-id derivation; adds tests.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/ui/src/components/chat/ChatInputArea.tsx Outdated
Comment thread src/ui/src/components/chat/PlanApprovalCard.tsx Outdated
Comment thread src/ui/src/types/chat.ts
Comment thread src/migrations/20260428034257_chat_message_author.sql Outdated
Comment thread src-tauri/src/commands/share.rs Outdated
Comment thread src-server/src/handler.rs Outdated
Comment thread src/ui/src/components/chat/ChatInputArea.tsx
Comment thread src/ui/src/components/chat/ChatInputArea.tsx Outdated
Comment thread src/ui/src/components/chat/MessagesWithTurns.tsx Outdated
Comment thread src-server/src/handler.rs
doomspork added a commit that referenced this pull request May 5, 2026
Addresses Copilot's review comments on PR #612 — three correctness
bugs, three doc-comment drifts, three i18n gaps, and a small hook
extraction that prevents the worst of the bugs from recurring.

Bug fixes:
- ChatInputArea.tsx: `lockedByOther` compared against `"host"`
  literal, which disabled the composer + Stop button for the actual
  turn holder on remote clients (their pid is the remote-issued
  string, not `"host"`). Now compares against the workspace's
  self-pid via `useSelfParticipantId`.
- PlanApprovalCard.tsx: same root cause in `ConsensusProgress` —
  the "(you)" label hardcoded `voter.id === "host"`, mislabelling
  the host as "you" on every remote viewer. Now compares against
  self-pid.
- handler.rs: server-side stream bridge declared `latest_usage`
  and consumed it via `take()` but never *populated* it — every
  assistant message persisted from a remote-initiated turn had
  null token counts. Mirror the Tauri-side capture pattern from
  `StreamEvent::Stream { InnerStreamEvent::MessageDelta }`.

Hook extraction:
- New `useSelfParticipantId(workspaceId)` in `src/ui/src/hooks/`
  centralizes the "for this workspace, who am I?" derivation that
  was inline-duplicated across four call sites. Future collab UI
  consumers can't accidentally hardcode `"host"` again.
- WorkspacePanelHeader, MessagesWithTurns, ChatInputArea, and
  PlanApprovalCard all now consume this hook.

Doc comment drifts (no behavior change):
- `types/chat.ts`: clarify `author_participant_id` is `"host"`
  (not NULL) for host-authored messages in collab sessions; NULL
  is reserved for solo / 1:1 / pre-collab rows.
- The migration's leading comment: same correction.
- `commands/share.rs`: replace the obsolete TODO comment that
  predated the `set_on_create` hook this PR added.

i18n:
- Add `composer_locked_placeholder`, `composer_locked_title`,
  `user_label` keys to `chat.json`.
- Replace hardcoded "Another user is asking…" / "Waiting for X
  to finish their turn" / "User" fallback strings with `t(...)`
  calls using the new keys.
Copilot AI review requested due to automatic review settings May 5, 2026 14:06
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 58 out of 58 changed files in this pull request and generated 8 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/ui/src/hooks/useAgentStream.ts
Comment thread src/ui/src/components/modals/ShareModal.tsx
Comment thread src/ui/src/components/settings/sections/CollaborationSettings.tsx
Comment thread src/model/chat_message.rs
Comment thread src-tauri/src/commands/chat/send.rs
Comment thread src-server/src/handler.rs Outdated
Comment thread src-tauri/src/commands/chat/send.rs Outdated
Comment thread src/ui/src/components/sidebar/Sidebar.tsx
doomspork added a commit that referenced this pull request May 5, 2026
Addresses 8 new Copilot comments on commit 0b077a8 — one security
fix, one correctness fix, four polish/consistency fixes, and two
i18n coverage gaps.

Security (#3189160026):
- read_plan_file now requires a chat_session_id and gates through
  ctx_authorize_chat_session, then checks the canonicalized path
  starts_with the workspace's worktree. A scoped share can no
  longer read plan files from other worktrees on the host.

Correctness (#3189160061):
- New prune_consensus_voters_for_session re-evaluates open plan
  votes when the participant set changes. Implicit-abstain
  semantics: a departed required-voter is dropped from
  required_voters AND has their cast vote removed; if remaining
  required_voters are now all approve, the vote finalizes. Wired
  via the room's participants-changed event in
  spawn_host_vote_resolver. Without this, a single disconnect
  could deadlock the agent indefinitely.

Polish:
- chat-message-added listener (#3189159789) now uses the shared
  selfParticipantIdForWorkspace helper instead of inlining the
  derivation. Hook + helper share one body so render-context and
  event-listener-context callers stay in lockstep.
- model/chat_message.rs (#3189159937): updated doc comment to
  match the same semantics already corrected in TS / SQL.
- turn-started broadcast (#3189159970) now uses
  resolve_host_display_name() instead of hardcoded "Host" so the
  user-visible composer-locked banner matches the chat-message
  author chip and the roster entry.
- ShareButton (#3189160105): drives off the new activeSharesCount
  store slice (hydrated at startup, kept in sync by ShareModal's
  refresh) instead of the legacy localServerRunning flag.

i18n:
- ShareModal (#3189159880): all user-facing strings moved to
  modals.json with proper t(...) calls.
- CollaborationSettings (#3189159922): all labels/descriptions/
  placeholders moved to settings.json.
Copilot AI review requested due to automatic review settings May 5, 2026 20:33
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 69 out of 69 changed files in this pull request and generated 6 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src-server/src/collab.rs
Comment thread src/ui/src/components/chat/PlanApprovalCard.tsx
Comment thread src/ui/src/components/chat/ParticipantsRoster.tsx
Comment thread src/ui/src/components/chat/ParticipantsRoster.tsx
Comment thread src/ui/src/components/chat/ParticipantsRoster.tsx
Comment thread src-tauri/src/commands/share.rs Outdated
@jamesbrink
Copy link
Copy Markdown
Member

LETS GO ALREADY!!!

jamesbrink pushed a commit that referenced this pull request May 6, 2026
Addresses Copilot's review comments on PR #612 — three correctness
bugs, three doc-comment drifts, three i18n gaps, and a small hook
extraction that prevents the worst of the bugs from recurring.

Bug fixes:
- ChatInputArea.tsx: `lockedByOther` compared against `"host"`
  literal, which disabled the composer + Stop button for the actual
  turn holder on remote clients (their pid is the remote-issued
  string, not `"host"`). Now compares against the workspace's
  self-pid via `useSelfParticipantId`.
- PlanApprovalCard.tsx: same root cause in `ConsensusProgress` —
  the "(you)" label hardcoded `voter.id === "host"`, mislabelling
  the host as "you" on every remote viewer. Now compares against
  self-pid.
- handler.rs: server-side stream bridge declared `latest_usage`
  and consumed it via `take()` but never *populated* it — every
  assistant message persisted from a remote-initiated turn had
  null token counts. Mirror the Tauri-side capture pattern from
  `StreamEvent::Stream { InnerStreamEvent::MessageDelta }`.

Hook extraction:
- New `useSelfParticipantId(workspaceId)` in `src/ui/src/hooks/`
  centralizes the "for this workspace, who am I?" derivation that
  was inline-duplicated across four call sites. Future collab UI
  consumers can't accidentally hardcode `"host"` again.
- WorkspacePanelHeader, MessagesWithTurns, ChatInputArea, and
  PlanApprovalCard all now consume this hook.

Doc comment drifts (no behavior change):
- `types/chat.ts`: clarify `author_participant_id` is `"host"`
  (not NULL) for host-authored messages in collab sessions; NULL
  is reserved for solo / 1:1 / pre-collab rows.
- The migration's leading comment: same correction.
- `commands/share.rs`: replace the obsolete TODO comment that
  predated the `set_on_create` hook this PR added.

i18n:
- Add `composer_locked_placeholder`, `composer_locked_title`,
  `user_label` keys to `chat.json`.
- Replace hardcoded "Another user is asking…" / "Waiting for X
  to finish their turn" / "User" fallback strings with `t(...)`
  calls using the new keys.
jamesbrink pushed a commit that referenced this pull request May 6, 2026
Addresses 8 new Copilot comments on commit 0b077a8 — one security
fix, one correctness fix, four polish/consistency fixes, and two
i18n coverage gaps.

Security (#3189160026):
- read_plan_file now requires a chat_session_id and gates through
  ctx_authorize_chat_session, then checks the canonicalized path
  starts_with the workspace's worktree. A scoped share can no
  longer read plan files from other worktrees on the host.

Correctness (#3189160061):
- New prune_consensus_voters_for_session re-evaluates open plan
  votes when the participant set changes. Implicit-abstain
  semantics: a departed required-voter is dropped from
  required_voters AND has their cast vote removed; if remaining
  required_voters are now all approve, the vote finalizes. Wired
  via the room's participants-changed event in
  spawn_host_vote_resolver. Without this, a single disconnect
  could deadlock the agent indefinitely.

Polish:
- chat-message-added listener (#3189159789) now uses the shared
  selfParticipantIdForWorkspace helper instead of inlining the
  derivation. Hook + helper share one body so render-context and
  event-listener-context callers stay in lockstep.
- model/chat_message.rs (#3189159937): updated doc comment to
  match the same semantics already corrected in TS / SQL.
- turn-started broadcast (#3189159970) now uses
  resolve_host_display_name() instead of hardcoded "Host" so the
  user-visible composer-locked banner matches the chat-message
  author chip and the roster entry.
- ShareButton (#3189160105): drives off the new activeSharesCount
  store slice (hydrated at startup, kept in sync by ShareModal's
  refresh) instead of the legacy localServerRunning flag.

i18n:
- ShareModal (#3189159880): all user-facing strings moved to
  modals.json with proper t(...) calls.
- CollaborationSettings (#3189159922): all labels/descriptions/
  placeholders moved to settings.json.
@jamesbrink jamesbrink force-pushed the doomspork/remote-share-investigation branch from 65e50dd to 7ef8a1e Compare May 6, 2026 04:24
Copilot AI review requested due to automatic review settings May 6, 2026 04:49
jamesbrink pushed a commit that referenced this pull request May 6, 2026
Addresses Copilot's review comments on PR #612 — three correctness
bugs, three doc-comment drifts, three i18n gaps, and a small hook
extraction that prevents the worst of the bugs from recurring.

Bug fixes:
- ChatInputArea.tsx: `lockedByOther` compared against `"host"`
  literal, which disabled the composer + Stop button for the actual
  turn holder on remote clients (their pid is the remote-issued
  string, not `"host"`). Now compares against the workspace's
  self-pid via `useSelfParticipantId`.
- PlanApprovalCard.tsx: same root cause in `ConsensusProgress` —
  the "(you)" label hardcoded `voter.id === "host"`, mislabelling
  the host as "you" on every remote viewer. Now compares against
  self-pid.
- handler.rs: server-side stream bridge declared `latest_usage`
  and consumed it via `take()` but never *populated* it — every
  assistant message persisted from a remote-initiated turn had
  null token counts. Mirror the Tauri-side capture pattern from
  `StreamEvent::Stream { InnerStreamEvent::MessageDelta }`.

Hook extraction:
- New `useSelfParticipantId(workspaceId)` in `src/ui/src/hooks/`
  centralizes the "for this workspace, who am I?" derivation that
  was inline-duplicated across four call sites. Future collab UI
  consumers can't accidentally hardcode `"host"` again.
- WorkspacePanelHeader, MessagesWithTurns, ChatInputArea, and
  PlanApprovalCard all now consume this hook.

Doc comment drifts (no behavior change):
- `types/chat.ts`: clarify `author_participant_id` is `"host"`
  (not NULL) for host-authored messages in collab sessions; NULL
  is reserved for solo / 1:1 / pre-collab rows.
- The migration's leading comment: same correction.
- `commands/share.rs`: replace the obsolete TODO comment that
  predated the `set_on_create` hook this PR added.

i18n:
- Add `composer_locked_placeholder`, `composer_locked_title`,
  `user_label` keys to `chat.json`.
- Replace hardcoded "Another user is asking…" / "Waiting for X
  to finish their turn" / "User" fallback strings with `t(...)`
  calls using the new keys.
jamesbrink pushed a commit that referenced this pull request May 6, 2026
Addresses 8 new Copilot comments on commit 0b077a8 — one security
fix, one correctness fix, four polish/consistency fixes, and two
i18n coverage gaps.

Security (#3189160026):
- read_plan_file now requires a chat_session_id and gates through
  ctx_authorize_chat_session, then checks the canonicalized path
  starts_with the workspace's worktree. A scoped share can no
  longer read plan files from other worktrees on the host.

Correctness (#3189160061):
- New prune_consensus_voters_for_session re-evaluates open plan
  votes when the participant set changes. Implicit-abstain
  semantics: a departed required-voter is dropped from
  required_voters AND has their cast vote removed; if remaining
  required_voters are now all approve, the vote finalizes. Wired
  via the room's participants-changed event in
  spawn_host_vote_resolver. Without this, a single disconnect
  could deadlock the agent indefinitely.

Polish:
- chat-message-added listener (#3189159789) now uses the shared
  selfParticipantIdForWorkspace helper instead of inlining the
  derivation. Hook + helper share one body so render-context and
  event-listener-context callers stay in lockstep.
- model/chat_message.rs (#3189159937): updated doc comment to
  match the same semantics already corrected in TS / SQL.
- turn-started broadcast (#3189159970) now uses
  resolve_host_display_name() instead of hardcoded "Host" so the
  user-visible composer-locked banner matches the chat-message
  author chip and the roster entry.
- ShareButton (#3189160105): drives off the new activeSharesCount
  store slice (hydrated at startup, kept in sync by ShareModal's
  refresh) instead of the legacy localServerRunning flag.

i18n:
- ShareModal (#3189159880): all user-facing strings moved to
  modals.json with proper t(...) calls.
- CollaborationSettings (#3189159922): all labels/descriptions/
  placeholders moved to settings.json.
@jamesbrink jamesbrink force-pushed the doomspork/remote-share-investigation branch from 7ef8a1e to 4ad316f Compare May 6, 2026 04:49
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 73 out of 73 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src-tauri/src/commands/chat/send.rs Outdated
Comment thread src/ui/src/components/chat/ParticipantsRoster.tsx Outdated
Comment thread src-server/src/ws.rs Outdated
Copilot AI review requested due to automatic review settings May 6, 2026 05:13
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 78 out of 78 changed files in this pull request and generated 4 comments.

Comment thread src/ui/src/hooks/useAgentStream.ts
Comment thread src-server/src/collab.rs Outdated
Comment thread src-tauri/src/commands/share.rs Outdated
Comment thread src-tauri/src/commands/share.rs Outdated
Copilot AI review requested due to automatic review settings May 6, 2026 05:25
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 83 out of 83 changed files in this pull request and generated 7 comments.

Comment thread src-tauri/src/commands/chat/interaction.rs
Comment thread src-tauri/src/commands/chat/interaction.rs
Comment thread src-tauri/src/commands/remote.rs Outdated
Comment thread src/ui/src/components/chat/userMessageAuthorLabel.ts
Comment thread src/ui/src/components/modals/ShareModal.tsx Outdated
Comment thread src-server/src/collab.rs Outdated
Comment thread src-tauri/src/commands/share.rs Outdated
Copilot AI review requested due to automatic review settings May 6, 2026 05:43
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 84 out of 84 changed files in this pull request and generated 3 comments.

Comment thread src/ui/src/components/chat/userMessageAuthorLabel.ts
Comment thread src-server/src/collab.rs Outdated
Comment thread src-tauri/src/commands/share.rs Outdated
Copilot AI review requested due to automatic review settings May 6, 2026 05:55
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 85 out of 85 changed files in this pull request and generated 4 comments.

Comment thread src/ui/src/components/chat/userMessageAuthorLabel.ts
Comment thread src/ui/src/components/chat/ChatInputArea.tsx
Comment thread scripts/macos-dev-app-runner.sh
Comment thread scripts/dev.sh
Copilot AI review requested due to automatic review settings May 6, 2026 06:55
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 91 out of 91 changed files in this pull request and generated 4 comments.

Comment thread src-server/src/ws.rs Outdated
Comment thread src-server/src/collab.rs Outdated
Comment thread scripts/macos-dev-app-runner.sh
Comment thread src/ui/src/components/chat/userMessageAuthorLabel.ts
Copilot AI review requested due to automatic review settings May 6, 2026 07:16
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 91 out of 91 changed files in this pull request and generated 2 comments.

Comment thread src-server/src/ws.rs Outdated
Comment thread src/ui/src/components/chat/userMessageAuthorLabel.ts
jamesbrink pushed a commit that referenced this pull request May 6, 2026
Addresses Copilot's review comments on PR #612 — three correctness
bugs, three doc-comment drifts, three i18n gaps, and a small hook
extraction that prevents the worst of the bugs from recurring.

Bug fixes:
- ChatInputArea.tsx: `lockedByOther` compared against `"host"`
  literal, which disabled the composer + Stop button for the actual
  turn holder on remote clients (their pid is the remote-issued
  string, not `"host"`). Now compares against the workspace's
  self-pid via `useSelfParticipantId`.
- PlanApprovalCard.tsx: same root cause in `ConsensusProgress` —
  the "(you)" label hardcoded `voter.id === "host"`, mislabelling
  the host as "you" on every remote viewer. Now compares against
  self-pid.
- handler.rs: server-side stream bridge declared `latest_usage`
  and consumed it via `take()` but never *populated* it — every
  assistant message persisted from a remote-initiated turn had
  null token counts. Mirror the Tauri-side capture pattern from
  `StreamEvent::Stream { InnerStreamEvent::MessageDelta }`.

Hook extraction:
- New `useSelfParticipantId(workspaceId)` in `src/ui/src/hooks/`
  centralizes the "for this workspace, who am I?" derivation that
  was inline-duplicated across four call sites. Future collab UI
  consumers can't accidentally hardcode `"host"` again.
- WorkspacePanelHeader, MessagesWithTurns, ChatInputArea, and
  PlanApprovalCard all now consume this hook.

Doc comment drifts (no behavior change):
- `types/chat.ts`: clarify `author_participant_id` is `"host"`
  (not NULL) for host-authored messages in collab sessions; NULL
  is reserved for solo / 1:1 / pre-collab rows.
- The migration's leading comment: same correction.
- `commands/share.rs`: replace the obsolete TODO comment that
  predated the `set_on_create` hook this PR added.

i18n:
- Add `composer_locked_placeholder`, `composer_locked_title`,
  `user_label` keys to `chat.json`.
- Replace hardcoded "Another user is asking…" / "Waiting for X
  to finish their turn" / "User" fallback strings with `t(...)`
  calls using the new keys.
jamesbrink pushed a commit that referenced this pull request May 6, 2026
Addresses 8 new Copilot comments on commit 0b077a8 — one security
fix, one correctness fix, four polish/consistency fixes, and two
i18n coverage gaps.

Security (#3189160026):
- read_plan_file now requires a chat_session_id and gates through
  ctx_authorize_chat_session, then checks the canonicalized path
  starts_with the workspace's worktree. A scoped share can no
  longer read plan files from other worktrees on the host.

Correctness (#3189160061):
- New prune_consensus_voters_for_session re-evaluates open plan
  votes when the participant set changes. Implicit-abstain
  semantics: a departed required-voter is dropped from
  required_voters AND has their cast vote removed; if remaining
  required_voters are now all approve, the vote finalizes. Wired
  via the room's participants-changed event in
  spawn_host_vote_resolver. Without this, a single disconnect
  could deadlock the agent indefinitely.

Polish:
- chat-message-added listener (#3189159789) now uses the shared
  selfParticipantIdForWorkspace helper instead of inlining the
  derivation. Hook + helper share one body so render-context and
  event-listener-context callers stay in lockstep.
- model/chat_message.rs (#3189159937): updated doc comment to
  match the same semantics already corrected in TS / SQL.
- turn-started broadcast (#3189159970) now uses
  resolve_host_display_name() instead of hardcoded "Host" so the
  user-visible composer-locked banner matches the chat-message
  author chip and the roster entry.
- ShareButton (#3189160105): drives off the new activeSharesCount
  store slice (hydrated at startup, kept in sync by ShareModal's
  refresh) instead of the legacy localServerRunning flag.

i18n:
- ShareModal (#3189159880): all user-facing strings moved to
  modals.json with proper t(...) calls.
- CollaborationSettings (#3189159922): all labels/descriptions/
  placeholders moved to settings.json.
@jamesbrink jamesbrink force-pushed the doomspork/remote-share-investigation branch from e4e39aa to ca2e0ee Compare May 6, 2026 07:33
Copilot AI review requested due to automatic review settings May 6, 2026 07:55
Add workspace-scoped sharing for remote Claudette sessions, including pairing-token grants, live share configuration, immediate revocation, and per-workspace RPC authorization.

Implement collaborative chat rooms with participant identity, host and remote roster state, turn locking, user-message broadcasting, agent-stream fanout, resync handling, and session snapshot hydration for reconnects.

Add consensus support for ExitPlanMode and AskUserQuestion, including required-voter snapshots, vote and answer propagation, late-joiner observer handling, muted and left participant pruning, plan approval restoration, and vote badges in the UI.

Synchronize remote client state for active turns, prompt timers, selected model, plan mode, forks, rollbacks, archived workspaces, and pending plan or question prompts.

Add the workspace sharing modal, collaboration settings, participant roster and moderation controls, self-participant labeling, remote-safe author labels, and localized collaboration, chat, and share strings.

Harden developer tooling by making the macOS hot-reload app runner and dev launcher restart cleanly without duplicate or stale-process kills.

Add coverage for remote session snapshots, collaborative answer and plan submission routing, author labeling, consensus vote rules, plan path snapshot lookup, checkpoint workspace detection, workspace archive propagation, and store collaboration state.
@jamesbrink jamesbrink force-pushed the doomspork/remote-share-investigation branch from 7ed4da1 to 25c94ce Compare May 6, 2026 08:00
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 91 out of 91 changed files in this pull request and generated 3 comments.

Comment thread src/ui/src/components/chat/ChatPanel.tsx
Comment thread src/ui/src/components/chat/AgentQuestionCard.tsx Outdated
Comment thread src/ui/src/App.tsx
Copilot AI review requested due to automatic review settings May 6, 2026 08:10
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 92 out of 92 changed files in this pull request and generated 1 comment.

Comment thread src-server/src/collab.rs
Copilot AI review requested due to automatic review settings May 6, 2026 08:25
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 92 out of 92 changed files in this pull request and generated 2 comments.

Comment thread src/db/chat.rs
Comment on lines +17 to +18
let prefix = &content[..marker_idx];
let start = prefix.rfind('/').unwrap_or(0);
Comment on lines 1411 to 1426
@@ -1303,6 +1425,12 @@ export function ChatPanel() {
// toolbar chip in sync).
setPlanMode(sid, false);
Re-pairing the same host (via the Nearby list or by pasting another
connection string) used to insert a fresh remote_connections row with
a new uuid every time, leaving the prior entry behind in the sidebar
holding a session token the server has rotated past. The user-visible
result was a stale "unusable" connection stacked under a fresh duplicate.

`pair_with_server` now looks up an existing row by (host, port) and
refreshes its name, session token, and cert fingerprint in place,
preserving id and created_at so the sidebar entry keeps its identity.
The frontend store action mirrors the backend by upserting by id, so
even paths that bypass list_remote_connections (mid-session re-pairs)
don't shadow the refreshed row with a duplicate.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants